namespace com.toolbox.alui.taglib
{

	using System;
	using System.Collections;
	using com.plumtree.openfoundation.util;
	using com.plumtree.openfoundation.web;
	using com.plumtree.portaluiinfrastructure.opener;
	using com.plumtree.portaluiinfrastructure.search;
	using com.plumtree.portaluiinfrastructure.tags;
	using com.plumtree.portaluiinfrastructure.tags.metadata;
	using com.plumtree.server;

	using com.plumtree.uiinfrastructure.activityspace;
	using com.plumtree.uiinfrastructure.constants;
	using com.plumtree.portaluiinfrastructure.statichelpers;
	using com.plumtree.xpshared.htmlconstructs;
	using com.plumtree.xpshared.htmlelements;
	using com.ppc.alui.common.helpers;
	using com.plumtree.openlog;

	/**
	 * This tag displays EOD properties for the requested object, as well as
	 * caching the data so that it can be reused throughout the request.
	 */
	public class EODTag : ATag
	{
		private static OpenLogger log = OpenLogService.GetLogger(
			OpenLogService.GetComponent(PTDebugHelpers.COMPONENT_PORTAL_UI_INFRASTRUCTURE),
			"com.toolbox.alui.taglib.EODTag");
	
		public static readonly ITagMetaData TAG;
		public static readonly OptionalTagAttribute CLASSID;
		public static readonly OptionalTagAttribute OBJECTID;
		public static readonly OptionalTagAttribute CURRENTUSER;
		public static readonly OptionalTagAttribute PROPNAME;
		public static readonly OptionalTagAttribute PROPID;

		// The prefix used to cache EOD properties in memory.
		public static readonly String STORAGE_PREFIX = "EOD";

		// The separator used to cache EOD properties in memory.
		public static readonly String STORAGE_SEPARATOR = ",";

		static EODTag()
		{
			// The TAG member variable needs to be initialized first since it
			// can be used in other tags (i.e. as a RequiredParentTag) that
			// are referenced by member variables of this tag (i.e. as a
			// RelatedChildTag).  This is necessary for tags that have
			// circular references such as a parent / child tag relationship.
			TAG = new TagMetaData("eodprop",
				"This tag displays EOD properties for the current user.");

			CLASSID = new OptionalTagAttribute("classid",
				"This attribute specifies the class ID of the object whose EOD properties will be retrieved.  " +
				"The class ID is required unless the current user is being accessed.",
				AttributeType.INT, "-1");

			OBJECTID = new OptionalTagAttribute("objectid",
				"This attribute specifies the object ID of the object whose EOD properties will be retrieved.  " +
				"The object ID is required unless the current user is being accessed.",
				AttributeType.INT, "-1");

			CURRENTUSER = new OptionalTagAttribute("currentuser",
				"This attribute causes the EOD properties to be retrieved from the current user." +
				"If this attribute is set to true the class ID and object ID will be ignored.",
				AttributeType.BOOLEAN, "false");

			PROPID = new OptionalTagAttribute("propid",
				"This attribute specifies the ID of the EOD property to be retrieved.  " +
				"The property ID is required unless the property name is being used.  " +
				"If an ID is present, the property name attribute will be ignored.",
				AttributeType.INT, "-1");

			PROPNAME = new OptionalTagAttribute("propname",
				"This attribute specifies the name of the EOD property to be retrieved.  " +
				"The property name is required unless the property ID is being used.",
				AttributeType.STRING, "");
		}

		/**
		 * @see com.plumtree.portaluiinfrastructure.tags.ATag#DisplayTag()
		 */
		public override HTMLElement DisplayTag()
		{
			int nClassID;
			int nObjectID;

			// Determine the proper object and class IDs to use.
			if (GetTagAttributeAsBoolean(CURRENTUSER))
			{
				IPTSession currentUser = (IPTSession) GetEnvironment().GetUserSession();

				nClassID = PT_CLASSIDS.PT_USER_ID;
				nObjectID = currentUser.GetUser().GetObjectID();
			} 
			else
			{
				nClassID = GetTagAttributeAsInt(CLASSID);
				nObjectID = GetTagAttributeAsInt(OBJECTID);

				if ((nClassID == -1) || (nObjectID == -1))
				{
					throw new XPException("Missing class ID or object ID when not accessing current user.");
				}
			}

			// Query for the EOD properties for this object.
			IPTQueryResult qr = GetCachedEODProperties(nClassID, nObjectID);

			// Lookup which EOD property to access.
			int nPropID = GetTagAttributeAsInt(PROPID);
			String strPropertyName = GetTagAttributeAsString(PROPNAME);

			// Look through the query result for the requested EOD property
			String strEODValue = LookupProperty(nPropID, strPropertyName, qr);

			if (null == strEODValue)
			{
				throw new XPException("Unable to find property by ID (" + nPropID + ") or by name (" +
					strPropertyName + ") for class ID " + nClassID + " and object ID " + nObjectID + ".");
			}

			HTMLElementCollection result = new HTMLElementCollection();

			result.AddInnerHTMLString(strEODValue);

			return result;
		}

		/**
		 * This method gets the cached EOD property data for a given object.
		 * If the data is not already present in the cache, another method is
		 * called to query for the data and then the data is cached.
		 * 
		 * @param _nClassID The class ID of the object to query.
		 * @param _nObjectID The object ID of the object to query.
		 * @return The EOD property data.
		 */
		protected IPTQueryResult GetCachedEODProperties(int _nClassID, int _nObjectID)
		{
			// Calculate the cache key for this object
			String strCacheKey;
			strCacheKey = STORAGE_PREFIX + _nClassID + STORAGE_SEPARATOR + _nObjectID;

			// Get the cached EOD properties off of the request scope cache.
			// This cache will be cleared after every HTTP Request, so we don't
			// need to worry about cache management.  If you want to increase cache
			// performance, the data can be cached on the session or application
			// scope.  However, that could possibly require periodic invalidation of
			// the cache data so it doesn't get out of date, as well as being aware
			// of multi-threading issues if the data is cached on the application
			// (and available to multiple users at the same time). 
			Object oCachedValue = GetStateVariable(strCacheKey, Scope.HTTP_REQUEST);

			IPTQueryResult qr;

			// Check if we already have a cached query result.
			if (null != oCachedValue)
			{
				qr = (IPTQueryResult) oCachedValue;
			} 
			else
			{
				// Query for the data since we do not have a cached query result.
				qr = GetEODProperties(_nClassID, _nObjectID);

				// Cache the query result so that it can be reused by other tags in this request.
				// NOTE: This will only cache the query result for the lifetime of the HTTP
				// request, after which it will be automatically cleaned up.  If an application
				// will be using a lot of EOD data in tags, and that data is not expected to change
				// much between requests, it will be faster to cache the query result on the session
				// or application.
				SetStateVariable(strCacheKey, qr, Scope.HTTP_REQUEST);
			}

			return qr;
		}

		/**
		 * This method queries for the EOD property data for a given object.
		 * 
		 * @param _nClassID The class ID of the object to query.
		 * @param _nObjectID The object ID of the object to query.
		 * @return The EOD property data.
		 */
		protected IPTQueryResult GetEODProperties(int _nClassID, int _nObjectID)
		{
			IPTQueryResult qr;

			// Get the object properties.
			IPTSession currentUser = (IPTSession) GetEnvironment().GetUserSession();
			IPTObjectManager objMgr = currentUser.GetObjectManagers(_nClassID);
			IPTObject curObject = (IPTObject) objMgr.Open(_nObjectID, false);
			IPTObjectProperties pProps = curObject.GetObjectProperties();

			// Query for the data.
			qr = pProps.GetPropertyData(PT_PROPIDS.PT_PROPID_OBJECTID |
				PT_PROPIDS.PT_PROPID_NAME | 
				PT_PROPIDS.PT_PROPID_PROP_VALUE);

			return qr;
		}

		/**
		 * This method looks up the requested property in the query result.
		 * 
		 * @param _nPropID The ID of the requested property (should be -1 if using name).
		 * @param _strPropertyName The name of the requested property (ignored if using ID).
		 * @param _qr The EOD property data.
		 * @return The value of the requestsed property.
		 */
		protected String LookupProperty(int _nPropID, String _strPropertyName, IPTQueryResult _qr)
		{
			String strEODValue = null;

			// Look through the query result for the requested EOD property
			for (int i = 0; i < _qr.RowCount(); i++)
			{
				if (_nPropID != -1)
				{
					// Lookup the property by ID
					int nCurrentPropID = _qr.ItemAsInt(i, PT_PROPIDS.PT_PROPID_OBJECTID);

					if (_nPropID == nCurrentPropID)
					{
						// Get the actual property value
						strEODValue = _qr.ItemAsString(i, PT_PROPIDS.PT_PROPID_PROP_VALUE);

						break;
					}
				} 
				else
				{
					// Lookup the property by name
					String strCurrentName = _qr.ItemAsString(i, PT_PROPIDS.PT_PROPID_NAME);

					if (0 == XPStringUtility.CompareIgnoreCase(_strPropertyName, strCurrentName))
					{
						// Get the actual property value
						strEODValue = _qr.ItemAsString(i, PT_PROPIDS.PT_PROPID_PROP_VALUE);

						break;
					}
				}
			}

			return strEODValue;
		}

		/**
		 * @see com.plumtree.portaluiinfrastructure.tags.ATag#GetTagType()
		 */
		public override TagType GetTagType()
		{
			// This tag does not display any of the HTML inside the tag,
			// and must be used as a singleton tag.
			return TagType.NO_BODY;
		}

		/**
		 * @see com.plumtree.portaluiinfrastructure.tags.ATag#SupportsAccessStyle(AccessStyles)
		 */
		public override bool SupportsAccessStyle(AccessStyles _style)
		{
			// This tag does not display any JavaScript, and therefore
			// supports all access styles.
			return true;
		}
		/**
		 * @see com.plumtree.portaluiinfrastructure.tags.ATag#Create()
		 */
		public override ATag Create()
		{
			return new EODTag();
		}
	}
}